package com.szy.assembly;

import java.util.ArrayList;
import java.util.HashMap;

public class Assembler {

    private String codeString;
    private Program program;

    private ArrayList<AssemblerError> errorList=new ArrayList<>();
    private HashMap<String,Integer> labelList=new HashMap<>();


    class UnknowLabel{
        int lineNumber;
        String originText;
        String label;
        int targetAddress;
        InstructionEnum opcode;
        String[] ops;
        public UnknowLabel(int lineNumber,String label,int targetAddress,InstructionEnum opcode,String[] ops,String originText){
            this.label=label;
            this.lineNumber=lineNumber;
            this.originText=originText;
            this.targetAddress=targetAddress;
            this.opcode=opcode;
            this.ops=ops;
        }
    }
    static class UnknowLabelException extends Exception{
        String label;
        public UnknowLabelException(String label){
            this.label=label;
        }
    }
    private ArrayList<UnknowLabel> unknowLabelList=new ArrayList<>();






    //public Assembler(){}

    public Assembler(String code){
        this.codeString=code;
    }

    private int getIntFromString(String s) throws UnknowLabelException{
        //先判断是不是标签
        if(labelList.containsKey(s)){
            return labelList.get(s);
        } else if(isLegalLabel(s)) {
            throw new UnknowLabelException(s);
        } else {
            return s.startsWith("0x")?Integer.parseInt(s.substring(2),16):Integer.parseInt(s);
        }
    }
    private boolean isLegalLabel(String label){
        char c0=label.charAt(0);
        if(c0 < 'a' || c0 > 'z'){
            return false;
        }
        for(int i=0;i<label.length();i++){
            char c=label.charAt(i);
            if(!((c>='a' && c<='z')||(c>='0' && c<='9'))) {
                return false;
            }
        }
        return true;
    }

    public boolean assemble(){
        program=new Program();
        errorList.clear();
        labelList.clear();
        unknowLabelList.clear();
        String codeNoSpace=codeString;
        codeNoSpace=codeNoSpace.replace('\t',' ');
        String[] lines=codeNoSpace.trim().split("\\r?\\n");


        //主程序起始地址，如果不写start标签，默认为0x20，上电从0x00开始执行，跳转至0x20
        program.setInstruction(0x00,new Instruction(InstructionEnum.J,0x20));

        //中断入口
        program.setInstruction(0x04,new Instruction(InstructionEnum.J,0x04));

        //开始翻译
        int instructionIndex=0x20;

        for(int i=0;i<lines.length;i++){
            String originCode=lines[i];
            //去掉最后的注释，分号标记，包含注释的原始代码在originCode
            int p=lines[i].indexOf(';');
            if(p>=0){
                lines[i]=lines[i].substring(0,p);
            }
            String[] part=lines[i].trim().split(":");

            if(part.length > 2){
                //有俩冒号了
                errorList.add(new AssemblerError(i+1,AssemblerErrorEnum.MANY_COLON,originCode));
                return false;
            }else {
                String body;
                if (part.length == 2) {
                    //有一个冒号，前面的是标签，先看看是不是合法的标签
                    if (isLegalLabel(part[0])) {
                        //重复的标签检查
                        if(labelList.containsKey(part[0])){
                            errorList.add(new AssemblerError(i+1,AssemblerErrorEnum.REPEAT_LABEL,originCode));
                            return false;
                        }
                        labelList.put(part[0], instructionIndex);

                        //检查特殊标签，起始地址，中断等
                        if(part[0].equals("start")){
                            program.setInstruction(0x00,new Instruction(InstructionEnum.J,instructionIndex).setOriginalText("start label"));
                        }else if(part[0].equals("ari")){
                            program.setInstruction(InterruptVector.entranceAddress.get(InterruptVector.InterruptName.ARITHMETIC),new Instruction(InstructionEnum.J,instructionIndex).setOriginalText("ari entry"));
                        }else if(part[0].equals("ext")){
                            program.setInstruction(InterruptVector.entranceAddress.get(InterruptVector.InterruptName.EXTERNAL),new Instruction(InstructionEnum.J,instructionIndex).setOriginalText("ext entry"));
                        }else if(part[0].equals("tim0")){
                            program.setInstruction(InterruptVector.entranceAddress.get(InterruptVector.InterruptName.TIMER_OVERFLOW_0),new Instruction(InstructionEnum.J,instructionIndex).setOriginalText("tim0 entry"));
                        }

                        //出现了一个新的标签之后，需要去看一下前面代码里那些找不到的标签，
                        //未知的标签都存在unknownLabelList，每次新标签识别出来遍历一遍
                        //如果刚好有对应的未知标签，就把那条指令写进program里
                        for(int j=0;j<unknowLabelList.size();j++){
                            UnknowLabel ul=unknowLabelList.get(j);
                            if(ul.label.equals(part[0])){
                                try {
                                    insertInstruction(ul.targetAddress,ul.opcode,ul.ops,(ul.lineNumber+1)+":"+ul.originText);

                                }catch (NumberFormatException e){
                                    errorList.add(new AssemblerError(ul.lineNumber+1,AssemblerErrorEnum.WRONG_OPERAND_FORMAT,ul.originText));
                                    return false;
                                }catch (UnknowLabelException e){
                                    //这里抛出说明一条指令中有至少两个后置标签，第一个查出来了，但还有一个未知，继续加入未知标签列表
                                    unknowLabelList.add(new UnknowLabel(ul.lineNumber+1,e.label,instructionIndex,ul.opcode,ul.ops,ul.originText));
                                }
                                unknowLabelList.remove(ul);
                                j--;
                            }
                        }
                    } else {
                        errorList.add(new AssemblerError(i+1, AssemblerErrorEnum.WRONG_LABEL_FORMAT,originCode));
                        return false;
                    }
                    body=part[1];
                }else{
                    body=part[0];
                }
                //body里就是去掉标签后面那部分
                if(body.equals("")){
                    //如果一行仅有一个标签或者是空行，那不用把他当成指令，标签记录的地址是：从这里开始后面第一条有内容的指令
                    //由于前面处理注释的地方trim过了，因此必定是空串
                    continue;
                }
                body=body.replace(',',' ');
                String[] ops=body.trim().split("\\s+");

                if(InstructionEnum.instructionRecognizer.containsKey(ops[0])){
                    InstructionEnum opcode = InstructionEnum.instructionRecognizer.get(ops[0]);
                    //检查操作数个数是否正确
                    int opcodeLen=InstructionEnum.opcodeLength.get(opcode);
                    if(ops.length - 1 != opcodeLen){
                        errorList.add(new AssemblerError(i+1, AssemblerErrorEnum.MISMATCH_OPERAND_NUM,originCode));
                        return false;
                    }
                    try {
                        insertInstruction(instructionIndex,opcode,ops,(i+1)+":"+originCode);

                    }catch (NumberFormatException e){
                        errorList.add(new AssemblerError(i+1,AssemblerErrorEnum.WRONG_OPERAND_FORMAT,originCode));
                        return false;
                    }catch (UnknowLabelException e){
                        unknowLabelList.add(new UnknowLabel(i+1,e.label,instructionIndex,opcode,ops,originCode));
                    }
                    instructionIndex++;

                }else{
                    errorList.add(new AssemblerError(i+1, AssemblerErrorEnum.WRONG_OPCODE,originCode));
                    return false;
                }


            }

        }
        //最后如果仍然有未知的标签，说明全文没出现过，报错
        if(unknowLabelList.isEmpty()){
            return true;
        }else{
            for (UnknowLabel ul : unknowLabelList) {
                errorList.add(new AssemblerError(ul.lineNumber, AssemblerErrorEnum.UNKNOW_LABEL, ul.originText));
            }
            return false;
        }


    }

    public void insertInstruction(int instructionIndex, InstructionEnum opcode, String ops[], String originCode) throws UnknowLabelException{
        switch (ops.length-1){
            case 0:
                program.setInstruction(instructionIndex,new Instruction(opcode).setOriginalText(originCode));
                break;
            case 1:
                program.setInstruction(instructionIndex,new Instruction(opcode,
                        getIntFromString(ops[1])).setOriginalText(originCode));
                break;
            case 2:
                program.setInstruction(instructionIndex,new Instruction(opcode,
                        getIntFromString(ops[1]),
                        getIntFromString(ops[2])).setOriginalText(originCode));
                break;
            case 3:
                program.setInstruction(instructionIndex,new Instruction(opcode,
                        getIntFromString(ops[1]),
                        getIntFromString(ops[2]),
                        getIntFromString(ops[3])).setOriginalText(originCode));
                break;
            case 4:
                program.setInstruction(instructionIndex,new Instruction(opcode,
                        getIntFromString(ops[1]),
                        getIntFromString(ops[2]),
                        getIntFromString(ops[3]),
                        getIntFromString(ops[4])).setOriginalText(originCode));
                break;
        }
    }
    public ArrayList<AssemblerError> getErrorList(){
        return errorList;
    }

    public Program getProgram(){
        return program;
    }
    public Assembler setCode(String code){
        this.codeString=code;
        return this;
    }
}
